#define EM_64_BIT
#define ENV_64_BIT

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>

#define CPU_SETSIZE __CPU_SETSIZE
#define CPU_ZERO(cpusetp) __CPU_ZERO_S (sizeof (cpu_set_t), cpusetp)
#define CPU_ISSET(cpu,cpusetp) __CPU_ISSET_S (cpu, sizeof (cpu_set_t), cpusetp)

#include <event_machine.h>
#include <event_machine/platform/env/environment.h>

int sched_getaffinity(pid_t pid, size_t cpusetsize,
                             cpu_set_t *mask);

#define NO_PATH(file_name) (strrchr((file_name), '/') ? \
			    strrchr((file_name), '/') + 1 : (file_name))


typedef enum startup_mode {
	STARTUP_ALL_CORES = 0,
	STARTUP_ONE_CORE_FIRST
} startup_mode_t;

typedef enum pktin_mode_t {
	DIRECT_RECV,
	PLAIN_QUEUE,
	SCHED_PARALLEL,
	SCHED_ATOMIC,
	SCHED_ORDERED
} pktin_mode_t;

typedef struct {
	struct {
		uint16_t device_id;
		int thread_per_core;
		int process_per_core;
		int core_count;
		em_core_mask_t phys_mask;
	} args_em;

	struct {
		char name[64];
		startup_mode_t startup_mode;
		uint32_t dispatch_rounds;
		struct {
			pktin_mode_t in_mode;
			bool pktin_vector;
			int if_count;
			char if_name[4][63 + 1];
			bool pktpool_em;
			bool pktpool_odp;
			bool vecpool_em;
			bool vecpool_odp;
		} pktio;
	} args_appl;
} parse_args_t;

typedef struct {
	int num_worker;
	odp_cpumask_t worker_mask;
} cpu_conf_t;

static void
verify_cpu_setup(const parse_args_t *parsed,
		 cpu_conf_t *cpu_conf /* out */)
{
	odp_cpumask_t invalid_mask;
	odp_cpumask_t check_mask;
	odp_cpumask_t zero_mask;
	int usable_cpus;
	cpu_set_t cpuset;

	const odp_cpumask_t *cpu_mask = &parsed->args_em.phys_mask.odp_cpumask;
	int num_cpus = parsed->args_em.core_count;

	if (num_cpus > 8)
		printf("Setup configured for max %d cores, not %d",
				  8, num_cpus);

	odp_cpumask_zero(&invalid_mask);
	odp_cpumask_zero(&check_mask);
	odp_cpumask_zero(&zero_mask);
	usable_cpus = 0;

	CPU_ZERO(&cpuset);
	sched_getaffinity(0, sizeof(cpuset), &cpuset);

	for (int i = 0; i < CPU_SETSIZE - 1; i++) {
		if (CPU_ISSET(i, &cpuset))
			usable_cpus++;
		else
			odp_cpumask_set(&invalid_mask, i);
	}

	odp_cpumask_and(&check_mask, &invalid_mask, cpu_mask);

	odp_cpumask_copy(&cpu_conf->worker_mask,
			 &parsed->args_em.phys_mask.odp_cpumask);
	cpu_conf->num_worker = parsed->args_em.core_count;
}

static void
parse_args(int argc, char *argv[], parse_args_t *parsed )
{
	static const struct option longopts[] = {
		{"coremask",         required_argument, NULL, 'c'},
		{"process-per-core", no_argument,       NULL, 'p'},
		{"thread-per-core",  no_argument,       NULL, 't'},
		{"device-id",        required_argument, NULL, 'd'},
		{"dispatch-rounds",  required_argument, NULL, 'r'},
		{"eth-interface",    required_argument, NULL, 'i'},
		{"pktpool-em",       no_argument,       NULL, 'e'},
		{"pktpool-odp",      no_argument,       NULL, 'o'},
		{"pktin-mode",       required_argument, NULL, 'm'},
		{"pktin-vector",     no_argument,       NULL, 'v'},
		{"startup-mode",     required_argument, NULL, 's'},
		{"vecpool-em",       no_argument,       NULL, 'x'},
		{"vecpool-odp",      no_argument,       NULL, 'y'},
		{"help",             no_argument,       NULL, 'h'},
		{NULL, 0, NULL, 0}
	};
	static const char *shortopts = "+c:ptd:r:i:oem:vs:xyh";
	long device_id = -1;

	parsed->args_appl.pktio.in_mode = DIRECT_RECV;
	parsed->args_appl.startup_mode = STARTUP_ALL_CORES;

	opterr = 0; 

	printf("EM application options:\n");

	while (1) {
		int opt;
		int long_index;

		opt = getopt_long(argc, argv, shortopts, longopts, &long_index);

		if (opt == -1)
			break; 

		switch (opt) {
		case 'c': { 
			char *mask_str = optarg;
			char tmp_str[EM_CORE_MASK_STRLEN];
			int err;

			em_core_mask_zero(&parsed->args_em.phys_mask);
			err = em_core_mask_set_str(mask_str,
						   &parsed->args_em.phys_mask);
			if (err)
				printf("Invalid coremask(%s) given",
						  mask_str);

			parsed->args_em.core_count =
			em_core_mask_count(&parsed->args_em.phys_mask);

			em_core_mask_tostr(tmp_str, sizeof(tmp_str),
					   &parsed->args_em.phys_mask);
			printf("  Coremask:     %s\n"
				   "  Core Count:   %i\n",
				   tmp_str, parsed->args_em.core_count);
		}
		break;

		case 'p': 
			parsed->args_em.process_per_core = 1;
			break;

		case 't':
			parsed->args_em.thread_per_core = 1;
			break;

		case 'd': {
			char *endptr;

			device_id = strtol(optarg, &endptr, 0);

			if (*endptr != '\0' ||
			    (uint64_t)device_id > UINT16_MAX)
				printf("Invalid device-id:%s",
						  optarg);

			parsed->args_em.device_id = (uint16_t)(device_id & 0xffff);
		}
		break;

		case 'r':
			parsed->args_appl.dispatch_rounds = atoi(optarg);
			if (atoi(optarg) < 0)
				printf("Invalid dispatch-rounds:%s",
						  optarg);
			break;

		case 'i': { 
			int i;
			size_t len, max;
			char *name;

			name = strtok(optarg, ",");
			for (i = 0; name != NULL; i++) {
				max = sizeof(parsed->args_appl.pktio.if_name[i]);
				len = strnlen(name, max);
				if (len + 1 > max)
					printf("Invalid if name:%s",
							  name);

				strncpy(parsed->args_appl.pktio.if_name[i], name, len);
				parsed->args_appl.pktio.if_name[i][len + 1] = '\0';

				name = strtok(NULL, ",");
			}
			parsed->args_appl.pktio.if_count = i;
		}
		break;

		case 'e':
			parsed->args_appl.pktio.pktpool_em = true;
			break;

		case 'o': 
			parsed->args_appl.pktio.pktpool_odp = true;
			break;

		case 'm': {
			int mode = atoi(optarg);

			if (mode == 0) {
				parsed->args_appl.pktio.in_mode = DIRECT_RECV;
			} else if (mode == 1) {
				parsed->args_appl.pktio.in_mode = PLAIN_QUEUE;
			} else if (mode == 2) {
				parsed->args_appl.pktio.in_mode = SCHED_PARALLEL;
			} else if (mode == 3) {
				parsed->args_appl.pktio.in_mode = SCHED_ATOMIC;
			} else if (mode == 4) {
				parsed->args_appl.pktio.in_mode = SCHED_ORDERED;
			} else {
				printf("Unknown value: -m, --pktin-mode = %d", mode);
			}
		}
		break;

		case 'v': { 
			parsed->args_appl.pktio.pktin_vector = true;
		}
		break;

		case 's': { 
			int mode = atoi(optarg);

			if (mode == 0) {
				parsed->args_appl.startup_mode = STARTUP_ALL_CORES;
			} else if (mode == 1) {
				parsed->args_appl.startup_mode = STARTUP_ONE_CORE_FIRST;
			} else {
				printf("Unknown value: -s, --startup-mode = %d", mode);
			}
		}
		break;

		case 'x': 
			parsed->args_appl.pktio.vecpool_em = true;
			break;

		case 'y':
			parsed->args_appl.pktio.vecpool_odp = true;
			break;

		case 'h': 
			exit(0);
			break;

		default:
			printf("Unknown option: %c!", opt);
			break;
		}
	}

	optind = 1;

	if (!parsed->args_em.core_count) {
		printf("Give mandatory coremask!");
	}

	if (device_id == -1) /* not set */
		parsed->args_em.device_id = 0;
	printf("  Device-id:    0x%" PRIX16 "\n", parsed->args_em.device_id);

	if (!(parsed->args_em.process_per_core ^ parsed->args_em.thread_per_core)) {
		printf("Select EITHER:\n"
				  "process-per-core(-p) OR thread-per-core(-t)!");
	}
	if (parsed->args_em.thread_per_core)
		printf("  EM mode:      Thread-per-core\n");
	else
		printf("  EM mode:      Process-per-core\n");

	const char *startup_mode_str = "";

	if (parsed->args_appl.startup_mode == STARTUP_ALL_CORES)
		startup_mode_str = "All EM-cores before application";
	else if (parsed->args_appl.startup_mode == STARTUP_ONE_CORE_FIRST)
		startup_mode_str = "One EM-core before application (then the rest)";

	printf("  Startup-mode: %s\n", startup_mode_str);

	size_t len = sizeof(parsed->args_appl.name);

	strncpy(parsed->args_appl.name, NO_PATH(argv[0]), len);
	parsed->args_appl.name[len - 1] = '\0';

	if (parsed->args_appl.pktio.if_count > 0) {
		if (parsed->args_appl.pktio.pktpool_em && parsed->args_appl.pktio.pktpool_odp) {
			printf("Select EITHER:\n"
					  "pktpool-em(-e) OR pktpool-odp(-o)!");
		}
		if (!parsed->args_appl.pktio.pktpool_em && !parsed->args_appl.pktio.pktpool_odp)
			parsed->args_appl.pktio.pktpool_em = true; /* default if none given */

		if (parsed->args_appl.pktio.pktpool_em)
			printf("  Pktio pool:   EM event-pool\n");
		else
			printf("  Pktio pool:   ODP pkt-pool\n");

		printf("  Pktin-mode:  -- \n");

		if (parsed->args_appl.pktio.pktin_vector) {
			printf("  Pktin-vector: Enabled\n");
			if (parsed->args_appl.pktio.vecpool_em &&
			    parsed->args_appl.pktio.vecpool_odp) {
				printf("Select EITHER:\n"
						  "vecpool-em(-x) OR vecpool-odp(-y)!");
			}
			if (!parsed->args_appl.pktio.vecpool_em &&
			    !parsed->args_appl.pktio.vecpool_odp)
				parsed->args_appl.pktio.vecpool_em = true; /* default */

			if (parsed->args_appl.pktio.vecpool_em)
				printf("  Vector pool:  EM vector-pool\n");
			else
				printf("  Vector pool:  ODP vector-pool\n");
		} else {
			printf("  Pktin-vector: Disabled\n");
			parsed->args_appl.pktio.vecpool_em = false;
			parsed->args_appl.pktio.vecpool_odp = false;
		}
	} else {
		printf("  Pktio:        Not used\n");
		parsed->args_appl.pktio.pktpool_em = false;
		parsed->args_appl.pktio.pktpool_odp = false;
		parsed->args_appl.pktio.vecpool_em = false;
		parsed->args_appl.pktio.vecpool_odp = false;
	}
}

static odp_instance_t
init_odp(const parse_args_t *parsed, const cpu_conf_t *cpu_conf)
{
	odp_init_t init_params;
	odp_instance_t instance;
	int ret;

	odp_init_param_init(&init_params);

	init_params.num_worker = cpu_conf->num_worker;
	init_params.worker_cpus = &cpu_conf->worker_mask;
	init_params.not_used.feat.cls = 1; 
	init_params.not_used.feat.compress = 1; 
	init_params.not_used.feat.crypto = 1; 
	init_params.not_used.feat.ipsec = 1;
	init_params.not_used.feat.tm = 1; 

	if (parsed->args_em.thread_per_core)
		init_params.mem_model = ODP_MEM_MODEL_THREAD;
	else
		init_params.mem_model = ODP_MEM_MODEL_PROCESS;

	ret = odp_init_global(&instance, &init_params, NULL);
	if (ret != 0)
		printf("ODP global init failed:%d", ret);

	ret = odp_init_local(instance, ODP_THREAD_CONTROL);
	if (ret != 0)
		printf("ODP local init failed:%d", ret);

	odp_schedule_config_t sched_config;

	odp_schedule_config_init(&sched_config);
	sched_config.sched_group.all = 0;
	sched_config.sched_group.control = 0;
	sched_config.sched_group.worker = 0;
	ret = odp_schedule_config(&sched_config);
	if (ret != 0)
		printf("ODP schedule config failed:%d", ret);


	odp_sys_info_print();

	return instance;
}

int main(int argc, char *argv[])
{
	if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
		printf("setvbuf() fails (errno(%i)=%s)", errno, strerror(errno));
	parse_args_t parsed;

	memset(&parsed, 0, sizeof(parsed));
	parse_args(argc, argv, &parsed/* out */);

	cpu_conf_t cpu_conf;

	memset(&cpu_conf, 0, sizeof(cpu_conf));
	verify_cpu_setup(&parsed, &cpu_conf/* out */);

	odp_instance_t instance;

	instance = init_odp(&parsed, &cpu_conf);

}

#define SPIN_COUNT  50000000

typedef struct {
	em_queue_t dest;
	unsigned int seq;
} hello_event_t;


typedef struct {
	em_eo_t this_eo;
	em_eo_t other_eo;
	int is_a;
	char name[16];
} my_eo_context_t;


typedef struct {
	em_queue_t queue;
} my_queue_context_t;

typedef struct {
	em_pool_t pool;
	my_eo_context_t eo_context_a;
	my_eo_context_t eo_context_b;
	my_queue_context_t queue_context_a;
	my_queue_context_t queue_context_b;
	em_queue_t queue_a;
	void *end[0] ENV_CACHE_LINE_ALIGNED;
} hello_shm_t;

COMPILE_TIME_ASSERT((sizeof(hello_shm_t) % ENV_CACHE_LINE_SIZE) == 0,
		    HELLO_SHM_T__SIZE_ERROR);


static ENV_LOCAL hello_shm_t *hello_shm;
static em_status_t hello_start(my_eo_context_t *eo_ctx, em_eo_t eo, const em_eo_conf_t *conf);
static em_status_t hello_stop(my_eo_context_t *eo_ctx, em_eo_t eo);
static void hello_receive_event(my_eo_context_t *eo_ctx, em_event_t event, em_event_type_t type, em_queue_t queue, my_queue_context_t *q_ctx);

void test_init(void)
{
	int core = em_core_id();

	if (core == 0) {
		hello_shm = env_shared_reserve("HelloSharedMem",
					       sizeof(hello_shm_t));
	} else {
		hello_shm = env_shared_lookup("HelloSharedMem");
	}

	if (hello_shm == NULL) {
			  printf("Hello init failed on EM-core: %u",
			   em_core_id());
	} else if (core == 0) {
		memset(hello_shm, 0, sizeof(hello_shm_t));
	}
}

void test_start()
{
	em_eo_t eo_a, eo_b;
	em_status_t ret, start_ret = EM_ERROR;
	char pool_name[EM_POOL_NAME_LEN];


	hello_shm->pool = EM_POOL_DEFAULT;

	em_pool_get_name(hello_shm->pool, pool_name, sizeof(pool_name));

	if(hello_shm->pool == EM_POOL_UNDEF)
		      printf("Undefined application event pool!\n");

	eo_a = em_eo_create("EO A",
			    (em_start_func_t)hello_start, NULL,
			    (em_stop_func_t)hello_stop, NULL,
			    (em_receive_func_t)hello_receive_event,
			    &hello_shm->eo_context_a);

	if(eo_a == EM_EO_UNDEF)printf("EO A creation failed!\n");

	eo_b = em_eo_create("EO B",
			    (em_start_func_t)hello_start, NULL,
			    (em_stop_func_t)hello_stop, NULL,
			    (em_receive_func_t)hello_receive_event,
			    &hello_shm->eo_context_b);

	hello_shm->eo_context_a.this_eo = eo_a;
	hello_shm->eo_context_a.other_eo = eo_b;
	hello_shm->eo_context_a.is_a = 1;

	hello_shm->eo_context_b.this_eo = eo_b;
	hello_shm->eo_context_b.other_eo = eo_a;
	hello_shm->eo_context_b.is_a = 0;

	em_eo_start_sync(eo_a, &start_ret, NULL);

	em_eo_start_sync(eo_b, &start_ret, NULL);
}

void test_stop()
{
	const int core = em_core_id();
	const em_eo_t eo_a = hello_shm->eo_context_a.this_eo;
	const em_eo_t eo_b = hello_shm->eo_context_b.this_eo;
	em_status_t stat;

	stat = em_eo_stop_sync(eo_a);
	if (stat != EM_OK)
		printf("EO A stop failed!");

	stat = em_eo_stop_sync(eo_b);
	if (stat != EM_OK)
		printf("EO B stop failed!");
}

void test_term(void)
{
	int core = em_core_id();

	printf("%s() on EM-core %d\n", __func__, core);

	if (core == 0) {
		env_shared_free(hello_shm);
		em_unregister_error_handler();
	}
}


static em_status_t hello_start(my_eo_context_t *eo_ctx, em_eo_t eo, const em_eo_conf_t *conf)
{
	em_queue_t queue;
	em_status_t status;
	my_queue_context_t *q_ctx;
	const char *queue_name;

	(void)conf;

	em_eo_get_name(eo, eo_ctx->name, sizeof(eo_ctx->name));

	if (eo_ctx->is_a) {
		queue_name = "queue A";
		q_ctx = &hello_shm->queue_context_a;
	} else {
		queue_name = "queue B";
		q_ctx = &hello_shm->queue_context_b;
	}

	queue = em_queue_create(queue_name, EM_QUEUE_TYPE_ATOMIC,
				EM_QUEUE_PRIO_NORMAL, EM_QUEUE_GROUP_DEFAULT,
				NULL);


	q_ctx->queue = queue;
	status = em_queue_set_context(queue, q_ctx);


	status = em_eo_add_queue_sync(eo, queue);

	if (eo_ctx->is_a) {
		/* Save queue ID for EO B. */
		hello_shm->queue_a = queue;
	} else {
		em_event_t event;
		hello_event_t *hello;

		event = em_alloc(sizeof(hello_event_t), EM_EVENT_TYPE_SW,
				 hello_shm->pool);


		hello = em_event_pointer(event);
		hello->dest = queue;
		hello->seq = 0;

		status = em_send(event, hello_shm->queue_a);

	}
	return EM_OK;
}


static em_status_t hello_stop(my_eo_context_t *eo_ctx, em_eo_t eo)
{
	my_queue_context_t *q_ctx;
	em_status_t stat = EM_OK;

	printf("Hello world stop on EM-core %d (%s, eo id %" PRI_EO ")\n",
		   em_core_id(), eo_ctx->name, eo);

	if (eo_ctx->is_a)
		q_ctx = &hello_shm->queue_context_a;
	else
		q_ctx = &hello_shm->queue_context_b;

	stat = em_eo_remove_queue_sync(eo, q_ctx->queue);
	if (stat != EM_OK)
		printf("EO remove queue failed!");

	stat = em_queue_delete(q_ctx->queue);
	if (stat != EM_OK)
		printf("Queue delete failed!");

	stat = em_eo_delete(eo);
	if (stat != EM_OK)
		printf("EO delete failed!");

	return stat;
}

static void hello_receive_event(my_eo_context_t *eo_ctx, em_event_t event,
		    em_event_type_t type, em_queue_t queue,
		    my_queue_context_t *q_ctx)
{
	em_queue_t dest;
	em_status_t status;
	hello_event_t *hello;
	(void)type;

	hello = em_event_pointer(event);

	dest = hello->dest;
	hello->dest = queue;

	printf("Hello world from %s!  My queue is %" PRI_QUEUE ".\t"
		   "I'm on core %02i.  Event seq is %u.\n",
		   eo_ctx->name, q_ctx->queue, em_core_id(), hello->seq++);

	status = em_send(event, dest);
	if (unlikely(status != EM_OK)) {
		em_free(event);
	}
}
